/*
 * Licensed to Jasig under one or more contributor license agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership. Jasig licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at the following location:
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package com.baijia.authentication;

import com.baijia.authentication.handler.AuthenticationHandler;
import com.baijia.authentication.handler.NamedAuthenticationHandler;
import com.baijia.authentication.handler.exception.AuthenticationException;
import com.baijia.authentication.handler.exception.UncategorizedAuthenticationException;
import com.baijia.authentication.principal.Credentials;
import com.baijia.authentication.principal.Principal;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.List;

import javax.validation.constraints.NotNull;

/**
 * @title AbstractAuthenticationManager
 * @desc 认证管理
 * @author caoliang
 * @date 2015年12月4日
 * @version 1.0
 */
public abstract class AbstractAuthenticationManager implements AuthenticationManager {

    protected final Logger logger = LoggerFactory.getLogger(AuthenticationManagerImpl.class);

    /** 可用的populator */
    @NotNull
    @Autowired
    private List<AuthenticationMetaDataPopulator> authenticationMetaDataPopulators =
        new ArrayList<AuthenticationMetaDataPopulator>();

    @Override
    public final Authentication authenticate(final Credentials credentials) throws AuthenticationException {

        final Pair<AuthenticationHandler, Principal> pair = authenticateAndObtainPrincipal(credentials);

        final Principal p = pair.getSecond();
        logger.info("{} authenticated {} with credential {}.", pair.getFirst(), p, credentials);
        logger.info("Attribute map for {}: {}", p.getId(), p.getAttributes());

        Authentication authentication = new MutableAuthentication(p);

        if (pair.getFirst() instanceof NamedAuthenticationHandler) {
            final NamedAuthenticationHandler a = (NamedAuthenticationHandler) pair.getFirst();
            authentication.getAttributes().put(AuthenticationManager.AUTHENTICATION_METHOD_ATTRIBUTE, a.getName());
        }

        for (final AuthenticationMetaDataPopulator authenticationMetaDataPopulator : this.authenticationMetaDataPopulators) {
            if (authenticationMetaDataPopulator.supports(credentials)) {
                authentication = authenticationMetaDataPopulator.populateAttributes(authentication, credentials);
            }
        }

        return new ImmutableAuthentication(authentication.getPrincipal(), authentication.getAttributes());
    }

    public final void setAuthenticationMetaDataPopulators(
        final List<AuthenticationMetaDataPopulator> authenticationMetaDataPopulators) {
        this.authenticationMetaDataPopulators = authenticationMetaDataPopulators;
    }

    /**
     * 按照认证流程，进行身份认证
     *
     * @param credentials 认证凭证
     * @return <handler,principal> 不能为空 认证流程不能完成时，抛出AuthenticationException
     */
    protected abstract Pair<AuthenticationHandler, Principal> authenticateAndObtainPrincipal(Credentials credentials)
        throws AuthenticationException;

    /**
     * 处理认证过程中发生的错误
     * 
     * @param handlerName handler 类名
     * @param credentials 认证凭证
     * @param e 异常
     */
    protected void handleError(final String handlerName, final Credentials credentials, final Exception e)
        throws AuthenticationException {
        if (e instanceof AuthenticationException) {
            logger.info("{} failed authenticating {}", handlerName, credentials);
            throw (AuthenticationException) e;
        }

        logger.error("{} threw error authenticating {}", handlerName, credentials, e);
        throw new UncategorizedAuthenticationException(e.getClass().getName(), e) {
            private static final long serialVersionUID = -3451425737598256946L;
        };
    }

    protected static class Pair<A, B> {

        private final A first;

        private final B second;

        public Pair(final A first, final B second) {
            this.first = first;
            this.second = second;
        }

        public A getFirst() {
            return this.first;
        }

        public B getSecond() {
            return this.second;
        }
    }

}
